/*--------------------------------------------------------------- 
 * Copyright (c) 1999,2000,2001,2002,2003                              
 * The Board of Trustees of the University of Illinois            
 * All Rights Reserved.                                           
 *--------------------------------------------------------------- 
 * Permission is hereby granted, free of charge, to any person    
 * obtaining a copy of this software (Iperf) and associated       
 * documentation files (the "Software"), to deal in the Software  
 * without restriction, including without limitation the          
 * rights to use, copy, modify, merge, publish, distribute,        
 * sublicense, and/or sell copies of the Software, and to permit     
 * persons to whom the Software is furnished to do
 * so, subject to the following conditions: 
 *
 *     
 * Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and 
 * the following disclaimers. 
 *
 *     
 * Redistributions in binary form must reproduce the above 
 * copyright notice, this list of conditions and the following 
 * disclaimers in the documentation and/or other materials 
 * provided with the distribution. 
 * 
 *     
 * Neither the names of the University of Illinois, NCSA, 
 * nor the names of its contributors may be used to endorse 
 * or promote products derived from this Software without
 * specific prior written permission. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTIBUTORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 * ________________________________________________________________
 * National Laboratory for Applied Network Research 
 * National Center for Supercomputing Applications 
 * University of Illinois at Urbana-Champaign 
 * http://www.ncsa.uiuc.edu
 * ________________________________________________________________ 
 *
 * Socket.cpp
 * by       Ajay Tirumala <tirumala@ncsa.uiuc.edu>
 * and      Mark Gates <mgates@nlanr.net>
 * ------------------------------------------------------------------- */

#define HEADERS()

#include "headers.h"
#include "SocketAddr.h"


#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* -------------------------------------------------------------------
 * Create a socket address. If inHostname is not null, resolve that
 * address and fill it in. Fill in the port number. Use IPv6 ADDR_ANY
 * if that is what is desired.
 * ------------------------------------------------------------------- */
void SockAddr_remoteAddr( thread_Settings *inSettings ) {
    SockAddr_zeroAddress( &inSettings->peer );
    if ( inSettings->mHost != NULL ) {
        SockAddr_setHostname( inSettings->mHost, &inSettings->peer, 
                              isIPV6( inSettings ) );
    } else {
#ifdef HAVE_IPV6
        if ( isIPV6( inSettings ) ) {
            ((struct sockaddr*)&inSettings->peer)->sa_family = AF_INET6;
        } else {
            ((struct sockaddr*)&inSettings->peer)->sa_family = AF_INET;
        }
    }

    if ( SockAddr_isIPv6( &inSettings->peer ) ) {
        inSettings->size_peer = sizeof( struct sockaddr_in6 );
    } else {
        inSettings->size_peer = sizeof( struct sockaddr_in );
    }
#else
        ((struct sockaddr*)&inSettings->peer)->sa_family = AF_INET;
    }
    inSettings->size_peer = sizeof( struct sockaddr_in );
#endif /* HAVE_IPV6 */
    SockAddr_setPort( &inSettings->peer, inSettings->mPort );
} // end SockAddr_remoteAddr

void SockAddr_localAddr( thread_Settings *inSettings ) {
    SockAddr_zeroAddress( &inSettings->local );
    if ( inSettings->mLocalhost != NULL ) {
        SockAddr_setHostname( inSettings->mLocalhost, &inSettings->local, 
                              isIPV6( inSettings ) );
    } else {
#ifdef HAVE_IPV6
        if ( isIPV6( inSettings ) ) {
            ((struct sockaddr*)&inSettings->local)->sa_family = AF_INET6;
        } else {
            ((struct sockaddr*)&inSettings->local)->sa_family = AF_INET;
        }
    }

    if ( SockAddr_isIPv6( &inSettings->local ) ) {
        inSettings->size_local = sizeof( struct sockaddr_in6 );
    } else {
        inSettings->size_local = sizeof( struct sockaddr_in );
    }
#else
        ((struct sockaddr*)&inSettings->local)->sa_family = AF_INET;
    }
        inSettings->size_local = sizeof( struct sockaddr_in );
#endif /* HAVE_IPV6 */
    SockAddr_setPort( &inSettings->local, inSettings->mPort );
} // end SockAddr_localAddr

/* -------------------------------------------------------------------
 * Resolve the hostname address and fill it in.
 * ------------------------------------------------------------------- */
void SockAddr_setHostname( const char* inHostname, 
                           iperf_sockaddr *inSockAddr, 
                           int isIPv6 ) {
    // ..I think this works for both ipv6 & ipv4... we'll see
#ifdef HAVE_IPV6
    {
        struct addrinfo *res, *itr;
        int ret_ga;

        ret_ga = getaddrinfo(inHostname, NULL, NULL, &res);
        FAIL( ret_ga, ( "error: %s\r\n", gai_strerror(ret_ga) ), NULL);
        FAIL( !res->ai_addr, ( "getaddrinfo failed to get an address... target was '%s'\r\n", inHostname), NULL);

        // Check address type before filling in the address
        // ai_family = PF_xxx; ai_protocol = IPPROTO_xxx, see netdb.h
        // ...but AF_INET6 == PF_INET6
        itr = res;
        if ( isIPv6 ) {
            // First check all results for a IPv6 Address
            while ( itr != NULL ) {
                if ( itr->ai_family == AF_INET6 ) {
                    memcpy(inSockAddr, (itr->ai_addr),
                           (itr->ai_addrlen));
                    freeaddrinfo(res);
                    return;
                } else {
                    itr = itr->ai_next;
                }
            }
        }
        itr = res;
        // Now find a IPv4 Address
        while ( itr != NULL ) {
            if ( itr->ai_family == AF_INET ) {
                memcpy(inSockAddr, (itr->ai_addr),
                       (itr->ai_addrlen));
                freeaddrinfo(res);
                return;
            } else {
                itr = itr->ai_next;
            }
        }
    }
#else
    // first try just converting dotted decimal
    // on Windows gethostbyname doesn't understand dotted decimal
    int rc = inet_pton( AF_INET, inHostname, 
                        (unsigned char*)&(((struct sockaddr_in*)inSockAddr)->sin_addr) );
//#if defined(WICED) && ( defined(NETWORK_NetX) || defined(NETWORK_NetX_Duo) )
//    if (rc != 0)
//    {
//        uint8_t* a = (uint8_t*)&((struct sockaddr_in*)inSockAddr)->sin_addr.s_addr;
//        ((struct sockaddr_in*)inSockAddr)->sin_addr.s_addr = a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3];
//    }
//#endif
    inSockAddr->sin_family = AF_INET;
    if ( rc == 0 ) {
        struct hostent *hostP = gethostbyname( inHostname );
        if ( hostP == NULL ) {
#if !(defined(WICED) && defined(RTOS_ThreadX))
            /* this is the same as herror() but works on more systems */
            const char* format;
            switch ( h_errno ) {
                case HOST_NOT_FOUND:
                    format = "%s: Unknown host\r\n";
                    break;
#ifdef NO_ADDRESS
                /* This causes compilation problems on LwIP. */
                case NO_ADDRESS:
                    format = "%s: No address associated with name\r\n";
                    break;
#endif /* NO_ADDRESS */
                case NO_DATA:
                    format = "%s: No data associated with name\r\n";
                    break;
                case NO_RECOVERY:
                    format = "%s: Unknown server error\r\n";
                    break;
                case TRY_AGAIN:
                    format = "%s: Host name lookup failure\r\n";
                    break;

                default:
                    format = "%s: Unknown resolver error\r\n";
                    break;
            }
            FAIL( 1, ( format, inHostname ), NULL );
#else
            FAIL( 1, ( "gethostbyname failed\r\n" ), NULL );
#endif /* #if !(defined(WICED) && defined(RTOS_ThreadX)) */
            return; // TODO throw
        }

        memcpy(&(((struct sockaddr_in*)inSockAddr)->sin_addr), *(hostP->h_addr_list),
               (hostP->h_length));
    }
#endif /* HAVE_IPV6 */
} // end SockAddr_setHostname

/* -------------------------------------------------------------------
 * Copy the IP address into the string.
 * ------------------------------------------------------------------- */
void SockAddr_getHostAddress( iperf_sockaddr *inSockAddr, char* outAddress, 
                                size_t len ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET ) {
        inet_ntop( AF_INET, &(((struct sockaddr_in*) inSockAddr)->sin_addr), 
                   outAddress, len);
    }
#ifdef HAVE_IPV6
    else {
        inet_ntop( AF_INET6, &(((struct sockaddr_in6*) inSockAddr)->sin6_addr), 
                   outAddress, len);
    }
#endif /* HAVE_IPV6 */
} // end SockAddr_getHostAddress

/* -------------------------------------------------------------------
 * Set the address to any (generally all zeros).
 * ------------------------------------------------------------------- */
void SockAddr_setAddressAny( iperf_sockaddr *inSockAddr ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET )
        memset( &(((struct sockaddr_in*) inSockAddr)->sin_addr), 0, 
                sizeof( struct in_addr ));
#ifdef HAVE_IPV6
    else
        memset( &(((struct sockaddr_in6*) inSockAddr)->sin6_addr), 0, 
                sizeof( struct in6_addr ));
#endif /* HAVE_IPV6 */
} // end SockAddr_setAddressAny

/* -------------------------------------------------------------------
 * Set the port to the given port. Handles the byte swapping.
 * ------------------------------------------------------------------- */
void SockAddr_setPort( iperf_sockaddr *inSockAddr, unsigned short inPort ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET )
        ((struct sockaddr_in*) inSockAddr)->sin_port = htons( inPort );
#ifdef HAVE_IPV6
    else
        ((struct sockaddr_in6*) inSockAddr)->sin6_port = htons( inPort );
#endif /* HAVE_IPV6 */
} // end SockAddr_setPort

/* -------------------------------------------------------------------
 * Set the port to zero, which lets the OS pick the port.
 * ------------------------------------------------------------------- */
void SockAddr_setPortAny( iperf_sockaddr *inSockAddr ) {
    SockAddr_setPort( inSockAddr, 0 );
} // end SockAddr_setPortAny

/* -------------------------------------------------------------------
 * Return the port. Handles the byte swapping.
 * ------------------------------------------------------------------- */
unsigned short SockAddr_getPort( iperf_sockaddr *inSockAddr ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET )
        return ntohs( ((struct sockaddr_in*) inSockAddr)->sin_port );
#ifdef HAVE_IPV6
    else
        return ntohs( ((struct sockaddr_in6*) inSockAddr)->sin6_port);
#endif /* HAVE_IPV6 */
    return 0;
} // end SockAddr_getPort

/* -------------------------------------------------------------------
 * Return the IPv4 Internet Address from the sockaddr_in structure
 * ------------------------------------------------------------------- */

struct in_addr* SockAddr_get_in_addr( iperf_sockaddr *inSockAddr ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET )
        return &(((struct sockaddr_in*) inSockAddr)->sin_addr);

    fprintf(stderr, "FATAL: get_in_addr called on IPv6 address\r\n");
    return NULL;
} // end SockAddr_get_in_addr

/* -------------------------------------------------------------------
 * Return the IPv6 Internet Address from the sockaddr_in6 structure
 * ------------------------------------------------------------------- */
#ifdef HAVE_IPV6
struct in6_addr* SockAddr_get_in6_addr( iperf_sockaddr *inSockAddr ) {
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET6 )
        return &(((struct sockaddr_in6*) inSockAddr)->sin6_addr);

    fprintf(stderr, "FATAL: get_in6_addr called on IPv4 address\r\n");
    return NULL;
} // end SockAddr_get_in6_addr
#endif /* HAVE_IPV6 */

/* -------------------------------------------------------------------
 * Return the size of the appropriate address structure.
 * ------------------------------------------------------------------- */
Socklen_t SockAddr_get_sizeof_sockaddr( iperf_sockaddr *inSockAddr ) {
#ifdef HAVE_IPV6
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET6 ) {
        return(sizeof(struct sockaddr_in6));
    }
#endif /* HAVE_IPV6 */
    return(sizeof(struct sockaddr_in));
} // end get_sizeof_sockaddr

/* -------------------------------------------------------------------
 * Return if IPv6 socket
 * ------------------------------------------------------------------- */
int SockAddr_isIPv6( iperf_sockaddr *inSockAddr ) {
#ifdef HAVE_IPV6
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET6 ) {
        return 1;
    }
#endif /* HAVE_IPV6 */
    return 0;
} // end SockAddr_isIPv6

/* -------------------------------------------------------------------
 * Return true if the address is a IPv4 multicast address.
 * ------------------------------------------------------------------- */
int SockAddr_isMulticast( iperf_sockaddr *inSockAddr ) {
#ifdef HAVE_IPV6
    if ( ((struct sockaddr*)inSockAddr)->sa_family == AF_INET6 ) {
        return( IN6_IS_ADDR_MULTICAST(&(((struct sockaddr_in6*) inSockAddr)->sin6_addr) ));
    } else
#endif /* HAVE_IPv6 */
    {
        // 224.0.0.0 to 239.255.255.255 (e0.00.00.00 to ef.ff.ff.ff)
        const unsigned long kMulticast_Mask = 0xe0000000L;

        return(kMulticast_Mask ==
               (ntohl( ((struct sockaddr_in*) inSockAddr)->sin_addr.s_addr) & kMulticast_Mask));
    }
} // end SockAddr_isMulticast

/* -------------------------------------------------------------------
 * Zero out the address structure.
 * ------------------------------------------------------------------- */
void SockAddr_zeroAddress( iperf_sockaddr *inSockAddr ) {
    memset( inSockAddr, 0, sizeof( iperf_sockaddr ));
} // end SockAddr_zeroAddress

/* -------------------------------------------------------------------
 * Compare two sockaddrs and return true if they are equal
 * ------------------------------------------------------------------- */
int SockAddr_are_Equal( struct sockaddr* first, struct sockaddr* second ) {
    if ( first->sa_family == AF_INET && second->sa_family == AF_INET ) {
        // compare IPv4 addresses
        return( ((long) ((struct sockaddr_in*)first)->sin_addr.s_addr == (long) ((struct sockaddr_in*)second)->sin_addr.s_addr)
                && ( ((struct sockaddr_in*)first)->sin_port == ((struct sockaddr_in*)second)->sin_port) );
    }
#ifdef HAVE_IPV6
    if ( first->sa_family == AF_INET6 && second->sa_family == AF_INET6 ) {
        // compare IPv6 addresses
        return( !memcmp(((struct sockaddr_in6*)first)->sin6_addr.s6_addr, ((struct sockaddr_in6*)second)->sin6_addr.s6_addr, sizeof(struct in6_addr)) 
                && (((struct sockaddr_in6*)first)->sin6_port == ((struct sockaddr_in6*)second)->sin6_port) );
    }
#endif /* HAVE_IPV6 */
    return 0;
} // end SockAddr_are_Equal

/* -------------------------------------------------------------------
 * Compare two sockaddrs and return true if the hosts are equal
 * ------------------------------------------------------------------- */
int SockAddr_Hostare_Equal( struct sockaddr* first, struct sockaddr* second ) {
    if ( first->sa_family == AF_INET && second->sa_family == AF_INET ) {
        // compare IPv4 addresses
        return( (long) ((struct sockaddr_in*)first)->sin_addr.s_addr == 
                (long) ((struct sockaddr_in*)second)->sin_addr.s_addr);
    }
#ifdef HAVE_IPV6    
    if ( first->sa_family == AF_INET6 && second->sa_family == AF_INET6 ) {
        // compare IPv6 addresses
        return( !memcmp(((struct sockaddr_in6*)first)->sin6_addr.s6_addr, 
                        ((struct sockaddr_in6*)second)->sin6_addr.s6_addr, sizeof(struct in6_addr)));
    }
#endif /* HAVE_IPV6 */
    return 0;
} // end SockAddr_Hostare_Equal

#ifdef __cplusplus
} /* end extern "C" */
#endif /* __cplusplus */
